來到了第12天,今日來講解第二個軟體設計常用實作-代理模式,也是解決重複出現程式碼的方案之一,話不多說來了解設計模式之一的代理模式
字面上的意思可以猜到目的是在「代替執行」,透過代理的方式操作實際實例,可以防止外部訪問到實際實例,由於在外部間隔一層代理類別,因此可以在實際物件調用方法的前後,添加其他執行的邏輯操作,代理模式的組成包含了下列三項
代理器(Proxy):具體代理實例,負責調用實際對象的實作方法
實際執行流程,先決定好代理主題要抽象的方法,在讓實際對象進行實作代理實作介面,最後在由代理器負責呼叫實際對象,實際實作如下
// 代理主題
interface Subject {
public void handler();
}
// 實作邏輯的被代理物件
class RealProxyA implements Subject {
public void handler() {
System.out.println("RealProxy運行業務邏輯");
}
}
// 代理器
class class Proxy {
public void run(Subject instance) {
System.out.println("準備代理實例運行方法");
// 執行主要的執行方法
instance.handler();
System.out.println("代理方法結束");
}
}
// Main.java
public static void main(String[] args){
Subject subjectA = new Subject();
Proxy proxy = new Proxy();
// 透過代理執行 run方法
proxy.run(subjectA);
}
這段程式碼運用代理器呼叫具體物件的方法,並在方法執行前後,添加其他業務邏輯,在詳細閱讀程式執行流程,可以得知代理器調用的代理方法將參數抽象化,在內部可以透過代理主題的抽象實作,實現核心邏輯的抽換,並在方法運行前後,添加了其他實作方法
雖然代理模式能加強複用性,並減少重複的程式碼,但設想需要代理的行為一旦變多,就需要製造多個代理模式核心元件,也要呼叫多個代理器且「複刻」相似結構的類別,這個做法違背了代理模式誕生的初衷,為了解決這個問題,衍伸出了動態代理的實作方式
動態代理實現方式,利用Java提供的reflectAPI內的InvocationHandler、Method和Proxy三種元件,實現真實類別方法調用,現在先來個簡單的動態代理
開頭先引用實現代理的反射物件
java.lang.reflect.InvocationHandler;
java.lang.reflect.Method;
java.lang.reflect.Proxy;
定義抽象代理主題
interface Subject {
public String handler();
}
建立真實代理物件類別
class Cat implements Subject {
@Override
public String handler() {
// System.out.println("貓咪叫");
String action = "學貓叫";
System.out.printf("布偶貓使用 瘋狂的%s\n", action);
return action;
}
}
建立動態代理執行器,負責真實代理物件的邏輯封裝
class ProxtHandler implements InvocationHandler {
// 實際被代理的物件,必須實作代理主題介面
private Object realInstance;
/**
* @param instance 實際要代理的物件
*/
public ProxtHandler(Object instance) {
this.realInstance = instance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 調用實際被代理的物件方法,並取得回傳內容
Object invokeResult = method.invoke(realInstance, args);
return invokeResult;
}
// 前處理
private void before() {
System.out.println("Before run handler");
}
// 後處理
private void after() {
System.out.println("After run handler");
}
}
最後在 Main方法實現相關邏輯
public static void main(String[] args) {
// 將被代理物件放照代理器裡面
Cat crazyRagdoll = new Cat();
ProxtHandler proxyHandler = new ProxtHandler(crazyRagdoll);
// Proxy工廠取得代理器實例
Subject proxyInstance = (Subject) Proxy.newProxyInstance(
// 載入要代理的類別參考
Subject.class.getClassLoader(),
// 要參考的類別
new Class<?>[] { Subject.class },
// 放 InvocationHandler 實作的實例
proxyHandler);
// 執行代理主題要執行的方法
Object result = proxyInstance.handler();
}
接著執行程式碼,成功打印「布偶貓,瘋狂的喵喵叫」字樣,現在將 before跟after方法放入 invoke方法內,改寫後的程式碼
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法調用前的邏輯
this.before();
// 調用實際被代理的物件方法,並取得回傳內容
Object invokeResult = method.invoke(realInstance, args);
// 方法調用後的邏輯
this.after();
return invokeResult;
}
實際運行程式,成功在原本的文字前後,添加了兩個方法內打印的字樣,看到這可能有人有疑問,動態代理與原本的靜態代理相似,只是多了反射類別提供的原件,將代理邏輯進行封裝,事實不以為然,仔細回頭看程式碼
Proxy.newProxyInstance API建立代理者的參數中,參考了多個 interface的規範,讓實際代理者能執行多個接口的方法,在不創建新的代理者的狀況下,這個做法能統一代理方法的調用,反觀靜態代理每次建立新的代理主題時,就要建立相應的代理器,來實現代理模式